package ace.cmp.application.autoconfig;

import ace.cmp.application.core.controller.GlobalErrorRestControllerAdvice;
import ace.cmp.application.core.property.AceApplicationProperties;
import ace.cmp.application.core.support.handler.WebExceptionHandler;
import ace.cmp.application.core.support.listener.PrintBeansApplicationReadyEventListener;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TimeZone;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;

/**
 * @author Caspar restful微服务架构，基础自动配置类 1. spring mvc
 */
@Slf4j
@EnableConfigurationProperties(AceApplicationProperties.class)
@Configuration
public class ApplicationAutoConfig implements WebMvcConfigurer {
  private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

  private final AceApplicationProperties aceApplicationProperties;
  private final ApplicationContext applicationContext;

  @Value("${spring.application.name:''}")
  private String applicationName;

  public ApplicationAutoConfig(
      AceApplicationProperties aceApplicationProperties, ApplicationContext applicationContext) {
    this.aceApplicationProperties = aceApplicationProperties;
    this.applicationContext = applicationContext;
  }

  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    // resolvers.add(new CustomServletModelAttributeMethodProcessor());
  }

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    //
    //        MappedInterceptor errorHttpStatusCodeWrapMappedInterceptor = new
    // MappedInterceptor(null, errorHttpStatusCodeWrapInterceptor);
    //        registry.addInterceptor(errorHttpStatusCodeWrapMappedInterceptor);

  }

  @Override
  public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}

  @Override
  public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.mediaType("json", MediaType.APPLICATION_JSON_UTF8);
    configurer.mediaType("html", MediaType.TEXT_HTML);
    configurer.mediaType("xml", MediaType.APPLICATION_XML);
    configurer.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
    // 扩展名至mimeType的映射,即 /user.json => application/json
    configurer.favorPathExtension(false);
    // 用于开启 /userinfo/123?format=json 的支持
    configurer.favorParameter(true);
    configurer.parameterName("mediaType");
    configurer.ignoreAcceptHeader(true);
    configurer.useRegisteredExtensionsOnly(true);
  }

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    //
    // registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
    //
    // registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    //        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    // registry.addResourceHandler("/vendor/graphiql/**").addResourceLocations("classpath:/static/vendor/graphiql/");
    //        registry.addResourceHandler("/favicon.ico").addResourceLocations("/favicon.ico");
    //
    // registry.addResourceHandler("/favicon.ico").addResourceLocations("/images/favicon.ico");
    //
    // registry.addResourceHandler("/favicon.png").addResourceLocations("/images/favicon.png");
    //
    // registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926).resourceChain(true)
    //                .addResolver(new GzipResourceResolver());
    //
    // registry.addResourceHandler("/images/**").addResourceLocations("/images/").setCachePeriod(31556926);
    //
    // registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926).resourceChain(true).addResolver(new GzipResourceResolver());
  }

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    HttpMessageConverters httpMessageConverters =
        this.aceHttpMessageConverters(this.objectMapper());
    converters.addAll(httpMessageConverters.getConverters());
  }

  @Bean
  public HttpMessageConverters aceHttpMessageConverters(ObjectMapper objectMapper) {
    HttpMessageConverters httpMessageConverters =
        new HttpMessageConverters(true, getConverters(objectMapper));
    return httpMessageConverters;
  }

  /**
   * enable default servlet
   *
   * @param configurer
   */
  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    // configurer.enable();
  }

  @Override
  public void addFormatters(FormatterRegistry registry) {
    DefaultFormattingConversionService defaultFormattingConversionService =
        (DefaultFormattingConversionService) registry;
    defaultFormattingConversionService.setEmbeddedValueResolver(
        new EmbeddedValueResolver(
            ((ConfigurableApplicationContext) this.applicationContext).getBeanFactory()));
  }

  @Override
  public void configureViewResolvers(ViewResolverRegistry registry) {
    //        registry.viewResolver(getJspViewResolver());
  }

  @Bean
  @ConditionalOnMissingBean
  public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(
      ObjectMapper objectMapper) {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(
        Arrays.asList(
            MediaType.APPLICATION_JSON_UTF8,
            MediaType.valueOf("application/x-www-form-urlencoded; charset=UTF-8"),
            MediaType.valueOf("text/plain;charset=UTF-8"),
            MediaType.valueOf("text/html;charset=UTF-8")));
    //        ObjectMapper objectMapper = this.objectMapper();
    converter.setObjectMapper(objectMapper);
    log.info("加载组件-MappingJackson2HttpMessageConverter组件完成");
    return converter;
  }

  @Bean
  @ConditionalOnMissingBean
  public StringHttpMessageConverter stringHttpMessageConverter() {
    log.info("加载组件-StringHttpMessageConverter组件完成");
    return new StringHttpMessageConverter(StandardCharsets.UTF_8);
  }

  @Bean
  @ConditionalOnMissingBean
  public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
    log.info("加载组件-ByteArrayHttpMessageConverter组件完成");
    return new ByteArrayHttpMessageConverter();
  }

  private ObjectMapper objectMapper() {
    log.info("加载组件-默认Json 序列化 ObjectMapper完成");
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_FORMAT));
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    javaTimeModule.addDeserializer(
        LocalDateTime.class,
        new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
    javaTimeModule.addSerializer(
        LocalDateTime.class,
        new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
    objectMapper.registerModules(javaTimeModule);
    objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
    objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);

    // WRAP_ROOT_VALUE(false) : 序列化的json是否显示根节点
    objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
    // INDENT_OUTPUT(false): 允许或禁止是否以缩进的方式展示json
    objectMapper.configure(SerializationFeature.INDENT_OUTPUT, false);
    // 当类的一个属性外部无法访问(如：没有getter setter 的私有属性)，
    // 且没有annotation 标明需要序列化时，如果FAIL_ON_EMPTY_BEANS 是true 将会跑出异常，如果是false 则不会跑出异常
    objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    // 如果POJO 中有一个直接自我引用，在序列化的时候会抛出 com.fasterxml.jackson.databind.JsonMappingException
    objectMapper.configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, true);
    // 如果序列化过程中，如果抛出 Exception 将会被包装，添加额外的上下文信息
    objectMapper.configure(SerializationFeature.WRAP_EXCEPTIONS, true);
    // value序列化日期以timestamps输出
    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    // key序列化日期以timestamps输出
    objectMapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
    // 序列化日期是否带上时区ID
    objectMapper.configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, true);
    // 序列化char[]时以json数组输出
    objectMapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS, false);
    // 序列化map时对key进行排序操作
    objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);

    //  反序列化时,遇到未知属性(那些没有对应的属性来映射的属性,
    //  并且没有任何setter或handler来处理这样的属性)时是否引起结果失败。
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    // 反序列化时,遇到null属性映射在java基本数据类型（int或douuble）是否报异常。
    objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
    // 反序列化时,遇到integer numbers属性映射在enum类型时，如果为true,numbers将不可以映射到enum中。
    objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false);
    // 反序列化时，遇到类名错误或者map中id找不到时是否报异常。
    objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true);
    // 反序列化时，遇到json数据存在两个相同的key时是否报异常。
    objectMapper.configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, false);
    // 反序列化时，遇到json属性字段为可忽略的是否报异常。
    objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
    return objectMapper;
  }

  private List<HttpMessageConverter<?>> getConverters(ObjectMapper objectMapper) {
    List<HttpMessageConverter<?>> myConverters = new ArrayList<>();
    myConverters.add(mappingJackson2HttpMessageConverter(objectMapper));
    myConverters.add(stringHttpMessageConverter());
    myConverters.add(byteArrayHttpMessageConverter());
    return myConverters;
  }

  /**
   * 底层异常解析，统一异常处理，不需要介入
   *
   * @return
   */
  @Bean
  public WebExceptionHandler webExceptionHandler() {
    return new WebExceptionHandler();
  }

  @Bean
  public PrintBeansApplicationReadyEventListener printBeansApplicationReadyEventListener() {
    PrintBeansApplicationReadyEventListener printBeansApplicationReadyEventListener =
        PrintBeansApplicationReadyEventListener.builder()
            .applicationName(this.applicationName)
            .printBeanNamesEnable(aceApplicationProperties.getBeanNamePrinterEnable())
            .printMappingHandlerEnable(aceApplicationProperties.getMappingHandlerPrinterEnable())
            .applicationContext(this.applicationContext)
            .build();
    return printBeansApplicationReadyEventListener;
  }

  @Bean
  public GlobalErrorRestControllerAdvice globalErrorRestControllerAdvice(
      WebExceptionHandler webExceptionHandler) {
    log.info("加载组件-GlobalErrorRestControllerAdvice统一异常处理组件完成");
    return new GlobalErrorRestControllerAdvice(webExceptionHandler);
  }
}
